using System;
using System.Collections.Generic;
using System.Linq;
using NRank.Algorithms;
using NRank.Algorithms.Strategies;
using NRank.Common;

namespace NRank
{
	/// <summary>
	/// Contains extension for collections so that advanced top-k algorithms can be used directly on 
	/// the collections.
	/// </summary>
	public static class RankedExtensions
	{
		/// <summary>
		/// Joins and sorts two collections over general condition
		/// </summary>
		/// <typeparam name="T1">Type of left source items</typeparam>
		/// <typeparam name="T2">Type of right source items</typeparam>
		/// <typeparam name="T">Type of result items</typeparam>
		/// <param name="leftSource">Left source</param>
		/// <param name="rightSource">Right source</param>
		/// <param name="rankFunc1">Ranking function for left items</param>
		/// <param name="rankFunc2">Ranking function for right items</param>
		/// <param name="joinFunc">Join function</param>
		/// <param name="joinCondition">Join condition</param>
		/// <param name="pullingStrategy">Pulling strategy</param>
		/// <param name="aggregateFunction">Aggregate function</param>
		/// <returns>Sorted-joined items</returns>
		public static IEnumerable<IRanked<T>> RankJoin<T1, T2, T>(
			this IEnumerable<T1> leftSource, 
			IEnumerable<T2> rightSource, 
			Func<T1, double> rankFunc1, 
			Func<T2, double> rankFunc2, 
			Func<T1, T2, T> joinFunc, 
			Func<T1, T2, bool> joinCondition, 
			PullingStrategyTypes pullingStrategy = PullingStrategyTypes.Alternating, 
			Func<double, double, double> aggregateFunction = null)
		{
			return new RankJoin<T1, T2, T>(leftSource, rightSource, rankFunc1, rankFunc2, joinFunc, pullingStrategy, aggregateFunction, joinCondition);
		}

		/// <summary>
		/// Joins and sorts two collections using Nested loop rank join over an equality condition
		/// </summary>
		/// <typeparam name="T1">Type of left source items</typeparam>
		/// <typeparam name="T2">Type of right source items</typeparam>
		/// <typeparam name="TField"></typeparam>
		/// <typeparam name="T">Type of result items</typeparam>
		/// <param name="leftSource">Left source</param>
		/// <param name="rightSource">Right source</param>
		/// <param name="rankFunc1">Ranking function for left items</param>
		/// <param name="rankFunc2">Ranking function for right items</param>
		/// <param name="joinFunc">Join function</param>
		/// <param name="equalityFieldSelector1">Equality field selector for left items</param>
		/// <param name="equalityFieldSelector2">Equality field selector for right items</param>
		/// <param name="aggregateFunc">Aggregate function</param>
		/// <returns>Sorted-joined items</returns>
		public static IEnumerable<IRanked<T>> NestedLoopRankJoin<T1, T2, TField, T>(
			this IEnumerable<T1> leftSource, 
			IEnumerable<T2> rightSource, 
			Func<T1, double> rankFunc1, 
			Func<T2, double> rankFunc2, 
			Func<T1, T2, T> joinFunc, 
			Func<T1, TField> equalityFieldSelector1,
			Func<T2, TField> equalityFieldSelector2,
			Func<double, double, double> aggregateFunc = null)
		{
			return new HashNestedLoopRankJoin<T1, T2, TField, T>(
				leftSource, rightSource, rankFunc1, rankFunc2, joinFunc, equalityFieldSelector1, equalityFieldSelector2, aggregateFunc);
		}

		/// <summary>
		/// Joins and sorts two collection using Nested loop rank join over a general condition
		/// </summary>
		/// <typeparam name="T1">Type of left source items</typeparam>
		/// <typeparam name="T2">Type of right source items</typeparam>
		/// <typeparam name="T">Type of result items</typeparam>
		/// <param name="leftSource">Left source</param>
		/// <param name="rightSource">Right source</param>
		/// <param name="rankFunc1">Ranking function for left items</param>
		/// <param name="rankFunc2">Ranking function for right items</param>
		/// <param name="joinFunc">Join function</param>
		/// <param name="joinCondition">Join condition</param>
		/// <param name="aggregateFunc">Aggregate function</param>
		/// <returns>Sorted-joined items</returns>
		public static IEnumerable<IRanked<T>> NestedLoopRankJoin<T1, T2, T>(
			this IEnumerable<T1> leftSource, 
			IEnumerable<T2> rightSource, 
			Func<T1, double> rankFunc1, 
			Func<T2, double> rankFunc2, 
			Func<T1, T2, T> joinFunc, 
			Func<T1, T2, bool> joinCondition, 
			Func<double, double, double> aggregateFunc = null)
		{
			return new NestedLoopRankJoin<T1, T2, T>(
				leftSource, rightSource, rankFunc1, rankFunc2, joinFunc, aggregateFunc, joinCondition);
		}

		/// <summary>
		/// Joins and sorts two collection over an equality condition using HashRankJoin algorithm
		/// </summary>
		/// <typeparam name="T1">Type of left source items</typeparam>
		/// <typeparam name="T2">Type of right source items</typeparam>
		/// <typeparam name="TField"></typeparam>
		/// <typeparam name="T">Type of result items</typeparam>
		/// <param name="leftSource">Left source</param>
		/// <param name="rightSource">Right source</param>
		/// <param name="rankFunc1">Ranking function for left items</param>
		/// <param name="rankFunc2">Ranking function for right items</param>
		/// <param name="joinFunc">Join function</param>
		/// <param name="equalityFieldSelector1">Equality field selector for left items</param>
		/// <param name="equalityFieldSelector2">Equality field selector for right items</param>
		/// <param name="pullingStrategy">Pulling strategy</param>
		/// <param name="aggregateFunc">Aggregate function</param>
		/// <param name="joinCondition">Join condition</param>
		/// <returns>Sorted-joined items</returns>
		public static IEnumerable<IRanked<T>> HashRankJoin<T1, T2, TField, T>(
			this IEnumerable<T1> leftSource, 
			IEnumerable<T2> rightSource, 
			Func<T1, double> rankFunc1, 
			Func<T2, double> rankFunc2, 
			Func<T1, TField> equalityFieldSelector1, 
			Func<T2, TField> equalityFieldSelector2, 
			Func<T1, T2, T> joinFunc, 
			PullingStrategyTypes pullingStrategy = PullingStrategyTypes.Alternating, 
			Func<double, double, double> aggregateFunc = null, 
			Func<T1, T2, bool> joinCondition = null)
		{
			return new HashRankJoin<T1, T2, TField, T>(
				leftSource, rightSource, equalityFieldSelector1, equalityFieldSelector2,
				rankFunc1, rankFunc2, joinFunc, pullingStrategy, aggregateFunc, joinCondition);
		}

		/// <summary>
		/// Convert collection of items to ranked items
		/// </summary>
		/// <typeparam name="T">Item type</typeparam>
		/// <param name="collection">Source collection</param>
		/// <param name="rankFunc">Ranking function</param>
		/// <returns>Ranked items</returns>
		public static IEnumerable<IRanked<T>> AsRanked<T>(this IEnumerable<T> collection, Func<T, double> rankFunc)
		{
			return collection.Select(n => n.AsRanked(rankFunc(n)));
		}

		/// <summary>
		/// Return inner objects from collection of ranked objects
		/// </summary>
		/// <typeparam name="T">Type of inner object</typeparam>
		/// <param name="collection">Source items</param>
		/// <returns>Unranked items</returns>
		public static IEnumerable<T> UnderlyingItems<T>(this IEnumerable<IRanked<T>> collection)
		{
			return collection.Select(c => c.Object);
		}

	}
}